﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ciloci.Flee;
using System.IO;
using System.Text.RegularExpressions;

namespace fleeEval
{
    class parser
    {
        //expression evaluator
        private ExpressionContext exp;
        private IDynamicExpression dyn;
        //current line stuff
        private string currentLine;
        private string primExp;
        private string innerExp;
        private int LineNum;
        private int WhileLoopNum;
        //language functions
        private language lang;
        //streamreader for if and while commands
        private List<string> input;
        //loop flags
        private bool loop;
        private int looppos;

        //generic constructor
        public parser()
        {
            this.currentLine = "";
            this.exp = new ExpressionContext();
            this.primExp = "";
            this.innerExp = "";
            this.lang = new language();
            this.loop = false;
            this.looppos = 0;
        }

        public parser(ref List<string> list, ref int linenum, ref int whileloopnum)
        {
            this.currentLine = "";
            this.exp = new ExpressionContext();
            this.primExp = "";
            this.innerExp = "";
            this.lang = new language();
            this.input = list;
            this.LineNum = linenum;
            this.WhileLoopNum = whileloopnum;
            this.loop = false;
            this.looppos = 0;
        }

        public void fetchLine(string currentline, ref int linenum, ref int whileline)
        {
            this.currentLine = currentline;
            this.currentLine = this.currentLine.Trim();
            this.LineNum = linenum;
            this.WhileLoopNum = whileline;
        }

        //exits on error, returns false on blankline and true on good line
        public bool checkInput(out int linenum)
        {
            if (this.currentLine == "" || this.currentLine == " " || this.currentLine.Contains("//") || this.currentLine == "{" || this.currentLine == "}")
            {
                if (this.currentLine == "}" && this.loop == true)
                {
                    linenum = this.looppos;
                    return false;
                }
                this.LineNum++;
                linenum = this.LineNum;
                return false;
            }
            if (this.currentLine.Contains("if") || this.currentLine.Contains("while"))
            {
                linenum = this.LineNum;
                return true;
            }
            if (!this.currentLine.Contains(';')) 
            {
                Console.WriteLine("error - line containing: " + this.currentLine + "does not end in a ;");
                Environment.Exit(1);
            }
            linenum = this.LineNum;
            return true;
        }

        //check if the line is evaluatable as is (eg, math equations etc)
        public bool checkEval()
        {
            string[] grrr = this.currentLine.Split(';');
            this.currentLine = grrr[0];
            if (this.currentLine.Contains('='))
            {
                string[] split = this.currentLine.Split('=');
                for (int i = 0; i < split.Length; i++)
                {
                    split[i] = split[i].Trim();
                }
                if (this.exp.Variables.ContainsKey(split[0]))
                {
                    try
                    {
                        this.dyn = exp.CompileDynamic(split[1]);
                        var return_val = dyn.Evaluate();
                        this.exp.Variables.Remove(split[0]);
                        this.exp.Variables.Add(split[0], return_val);
                        return true;
                    }
                    catch
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            try
            {
                this.dyn = exp.CompileDynamic(this.currentLine);
                var return_val = dyn.Evaluate();
                Console.WriteLine(return_val);
                return true;
            }
            catch
            {
                return false;
            }
        }

        //check to make sure that there is a valid command on this line, this may be removed pending addition of features
        public bool checkSyntax()
        {
            Match match = Regex.Match(this.currentLine, @"print|get|if|while|var|inc|dec");
            if (match.Success)
            {
                return true;
            }
            else
            {
                Console.WriteLine("error - line: " + this.currentLine + "does not contain valid syntax.");
                Environment.Exit(1);
                return false;
            }
        }

        //custom string splitter, this is used to get the expression inside of the brackets
        public void splitString()
        {
            //primary expression
            string[] splitarray = this.currentLine.Split('(');
            this.primExp = splitarray[0];

            //get inner expression
            int start = this.currentLine.IndexOf('(') + 1;
            int end = this.currentLine.IndexOf(')');
            this.innerExp = this.currentLine.Substring(start, end - start);           
        }

        //call the appropriate functions here
        public bool eval(out int linenum, out int whilenum)
        {
            switch(this.primExp)
            {
                case "print":
                    this.lang.print(this.innerExp, ref this.exp);
                    linenum = this.LineNum;
                    whilenum = 0;
                    return true;
                case "get":
                    this.lang.getVar(this.innerExp, ref this.exp);
                    break;
                case "if":
                    bool Ievaluated = false;
                    try
                    {
                        this.dyn = this.exp.CompileDynamic(this.innerExp);
                        Ievaluated = (bool)this.dyn.Evaluate();
                    }
                    catch
                    {
                        Console.WriteLine("expression: " + this.innerExp + " cannot be evaluated as a boolean expression.");
                        Environment.Exit(1);
                    }
                    if (Ievaluated == true)
                    {
                        break;
                    }
                    else
                    {
                        while (this.input[this.LineNum] != "}")
                        {
                            this.LineNum++;
                            linenum = this.LineNum;
                        }
                    }
                    break;
                case "while":
                    bool Wevaluated = false;
                    try
                    {
                        this.dyn = this.exp.CompileDynamic(this.innerExp);
                        Wevaluated = (bool)this.dyn.Evaluate();
                    }
                    catch
                    {
                        Console.WriteLine("expression: " + this.innerExp + " cannot be evaluated as a boolean expression.");
                        Environment.Exit(1);
                    }
                    if (Wevaluated == true)
                    {
                        whilenum = this.LineNum;
                        linenum = this.LineNum;
                        this.loop = true;
                        this.looppos = this.LineNum;
                        return true;
                    }
                    else
                    {
                        this.loop = false;
                        this.looppos = 0;
                        break;
                    }
                case "var":
                    string[] splitExp = this.innerExp.Split(',');
                    splitExp[1] = splitExp[1].Trim();
                    var secondaryExp = this.checkType(splitExp[1]);
                    this.exp.Variables[splitExp[0]] = secondaryExp;
                    break;
                case "inc":
                    int Inum = 0;
                    try
                    {
                        Inum = (int)this.exp.Variables[innerExp];
                    }
                    catch
                    {
                        Console.WriteLine("error, cannot increment a non-number variable");
                        Environment.Exit(1);
                    }
                    Inum++;
                    this.exp.Variables[innerExp] = Inum;
                    break;
                case "dec":
                    int Dnum = 0;
                    try
                    {
                        Dnum = (int)this.exp.Variables[innerExp];
                    }
                    catch
                    {
                        Console.WriteLine("error, cannot decrement a non-number variable");
                        Environment.Exit(1);
                    }
                    Dnum--;
                    this.exp.Variables[innerExp] = Dnum;
                    break;
                default:
                    Console.WriteLine("error parsing line.");
                    Environment.Exit(1);
                    linenum = this.LineNum;
                    whilenum = 0;
                    return false;
            }
            linenum = this.LineNum;
            whilenum = 0;
            return true;
        }

        /*a simplistic method of figuring out what variables type a variable is
         * basically, it will keep trying to cast a variable to a type until it
         * reaches the bottom and the compiler will then store it as a string.
         */
        public object checkType(string item)
        {
            try
            {
                return int.Parse(item);
            }
            catch
            {
                try
                {
                    return float.Parse(item);
                }
                catch
                {
                    try
                    {
                        return bool.Parse(item);
                    }
                    catch
                    {
                        return item;
                    }
                }
            }
        }
    }
}
